All files / core/util/libs parser.ts

97.3% Statements 72/74
93.02% Branches 40/43
100% Functions 4/4
97.26% Lines 71/73
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198                                12x 12x 12x             18x 18x 18x 42x 9x 9x 9x   9x     18x               49x 49x 2x   50x 50x 47x   3x 3x 3x         49x               12x     49x 49x   49x                 49x       2x         47x 4x     47x   47x                               12x                       49x 49x 49x 49x     49x 49x 49x     49x   49x 49x 48x 48x       49x 49x 31x   49x 49x 47x   49x 49x   18x   49x         49x 49x 4x 4x   45x     49x 49x   44x 44x 5x 1x 4x 3x     49x 1x       49x                    
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { Path } from '../Path';
import { RepoInfo } from '../../RepoInfo';
import { warnIfPageIsSecure, warn, fatal } from '../util';
 
/**
 * @param {!string} pathString
 * @return {string}
 */
function decodePath(pathString: string): string {
  let pathStringDecoded = '';
  const pieces = pathString.split('/');
  for (let i = 0; i < pieces.length; i++) {
    if (pieces[i].length > 0) {
      let piece = pieces[i];
      try {
        piece = decodeURIComponent(piece.replace(/\+/g, ' '));
      } catch (e) {}
      pathStringDecoded += '/' + piece;
    }
  }
  return pathStringDecoded;
}
 
/**
 * @param {!string} queryString
 * @return {!{[key:string]:string}} key value hash
 */
function decodeQuery(queryString: string): { [key: string]: string } {
  let results = {};
  if (queryString.startsWith('?')) {
    queryString = queryString.substring(1);
  }
  for (const segment of queryString.split('&')) {
    if (segment.length === 0) {
      continue;
    }
    const kv = segment.split('=');
    Eif (kv.length === 2) {
      results[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
    } else {
      warn(`Invalid query segment '${segment}' in query '${queryString}'`);
    }
  }
  return results;
}
 
/**
 *
 * @param {!string} dataURL
 * @return {{repoInfo: !RepoInfo, path: !Path}}
 */
export const parseRepoInfo = function(
  dataURL: string
): { repoInfo: RepoInfo; path: Path } {
  const parsedUrl = parseURL(dataURL),
    namespace = parsedUrl.subdomain;
 
  Iif (parsedUrl.domain === 'firebase') {
    fatal(
      parsedUrl.host +
        ' is no longer supported. ' +
        'Please use <YOUR FIREBASE>.firebaseio.com instead'
    );
  }
 
  // Catch common error of uninitialized namespace value.
  if (
    (!namespace || namespace == 'undefined') &&
    parsedUrl.domain !== 'localhost'
  ) {
    fatal(
      'Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com'
    );
  }
 
  if (!parsedUrl.secure) {
    warnIfPageIsSecure();
  }
 
  const webSocketOnly = parsedUrl.scheme === 'ws' || parsedUrl.scheme === 'wss';
 
  return {
    repoInfo: new RepoInfo(
      parsedUrl.host,
      parsedUrl.secure,
      namespace,
      webSocketOnly
    ),
    path: new Path(parsedUrl.pathString)
  };
};
 
/**
 *
 * @param {!string} dataURL
 * @return {{host: string, port: number, domain: string, subdomain: string, secure: boolean, scheme: string, pathString: string}}
 */
export const parseURL = function(
  dataURL: string
): {
  host: string;
  port: number;
  domain: string;
  subdomain: string;
  secure: boolean;
  scheme: string;
  pathString: string;
} {
  // Default to empty strings in the event of a malformed string.
  let host = '',
    domain = '',
    subdomain = '',
    pathString = '';
 
  // Always default to SSL, unless otherwise specified.
  let secure = true,
    scheme = 'https',
    port = 443;
 
  // Don't do any validation here. The caller is responsible for validating the result of parsing.
  Eif (typeof dataURL === 'string') {
    // Parse scheme.
    let colonInd = dataURL.indexOf('//');
    if (colonInd >= 0) {
      scheme = dataURL.substring(0, colonInd - 1);
      dataURL = dataURL.substring(colonInd + 2);
    }
 
    // Parse host, path, and query string.
    let slashInd = dataURL.indexOf('/');
    if (slashInd === -1) {
      slashInd = dataURL.length;
    }
    let questionMarkInd = dataURL.indexOf('?');
    if (questionMarkInd === -1) {
      questionMarkInd = dataURL.length;
    }
    host = dataURL.substring(0, Math.min(slashInd, questionMarkInd));
    if (slashInd < questionMarkInd) {
      // For pathString, questionMarkInd will always come after slashInd
      pathString = decodePath(dataURL.substring(slashInd, questionMarkInd));
    }
    let queryParams = decodeQuery(
      dataURL.substring(Math.min(dataURL.length, questionMarkInd))
    );
 
    // If we have a port, use scheme for determining if it's secure.
    colonInd = host.indexOf(':');
    if (colonInd >= 0) {
      secure = scheme === 'https' || scheme === 'wss';
      port = parseInt(host.substring(colonInd + 1), 10);
    } else {
      colonInd = dataURL.length;
    }
 
    const parts = host.split('.');
    if (parts.length === 3) {
      // Normalize namespaces to lowercase to share storage / connection.
      domain = parts[1];
      subdomain = parts[0].toLowerCase();
    } else if (parts.length === 2) {
      domain = parts[0];
    } else if (parts[0].slice(0, colonInd).toLowerCase() === 'localhost') {
      domain = 'localhost';
    }
    // Support `ns` query param if subdomain not already set
    if (subdomain === '' && 'ns' in queryParams) {
      subdomain = queryParams['ns'];
    }
  }
 
  return {
    host,
    port,
    domain,
    subdomain,
    secure,
    scheme,
    pathString
  };
};